/**
 * Licensed under the Apache License Version 2.0.
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package net.interknit.knitter.fw;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.CharArrayReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilterInputStream;
import java.io.FilterReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.PushbackInputStream;
import java.io.PushbackReader;
import java.io.Reader;
import java.io.StringReader;

public class DefaultIOAdapter implements IOAdapter
{
	@Override
	public <T extends InputStream> T toInputStream(byte[] bytes, Class<T> valueType)
	throws IOException
	{
		InputStream returnValue = null;

		ByteArrayInputStream bais = new ByteArrayInputStream(bytes);

		// create an instance of InputStream of type T (sent from setValue()
		// method) and populate it with 'value'
		if(valueType.equals(InputStream.class))
			returnValue = bais;
		else if(valueType.equals(ByteArrayInputStream.class))
			returnValue = bais;
		else if(valueType.equals(BufferedInputStream.class))
			returnValue = new BufferedInputStream(bais);
		else if(valueType.equals(DataInputStream.class))
			returnValue = new DataInputStream(bais);
		else if(valueType.equals(FilterInputStream.class))
			returnValue = new BufferedInputStream(bais);
		else if(valueType.equals(ObjectInputStream.class))
			returnValue = new ObjectInputStream(bais);
		else if(valueType.equals(PushbackInputStream.class))
			returnValue = new PushbackInputStream(bais);
		else if(valueType.equals(FileInputStream.class))
			returnValue = createFileInputStream(bytes);
		// try creating a new instance of the custom InputStream object
		else
			returnValue = createCustomInputStream(bytes, valueType);

		if(returnValue == null)
			throw new RuntimeException(valueType.getName() + " " +
					"is an unknown subclass of InputStream; a custom " +
					"IOAdapter must be created to provide an implementation " +
					"of toInputStream() method");

		return (T) returnValue;
	}

	@Override
	public <T extends Reader> T toReader(char[] chars, Class<T> valueType)
	throws IOException
	{
		Reader returnValue = null;

		CharArrayReader car = new CharArrayReader(chars);

		// create an instance of Reader of type T (sent from setValue()
		// method) and populate it with 'value'
		if(valueType.equals(Reader.class))
			returnValue = car;
		else if(valueType.equals(CharArrayReader.class))
			returnValue = car;
		else if(valueType.equals(BufferedReader.class))
			returnValue = new BufferedReader(car);
		else if(valueType.equals(FilterReader.class))
			returnValue = new PushbackReader(car);
		else if(valueType.equals(PushbackReader.class))
			returnValue = new PushbackReader(car);
		else if(valueType.equals(StringReader.class))
			returnValue = new StringReader(new String(chars));
		else if(valueType.equals(FileReader.class))
			returnValue = createFileReader(chars);
		// try creating a new instance of the custom Reader object
		else
			returnValue = createCustomReader(chars, valueType);

		if(returnValue == null)
			throw new RuntimeException(valueType.getName() + " " +
					"is an unknown subclass of Reader; a custom " +
					"IOAdapter must be created to provide an implementation " +
					"of toReader() method");

		return (T) returnValue;
	}

	// create a FileInputStream from the given byte[] by creating a temporary
	// file and reading that file in a FileInputStream
	private FileInputStream createFileInputStream(byte[] bytes) throws IOException
	{
		File tempFile = File.createTempFile("cfis", "bytes");

		FileOutputStream fos = new FileOutputStream(tempFile);
		fos.write(bytes);
		fos.close();

		return new FileInputStream(tempFile);
	}

	// create a FileReader from the given char[] by creating a temporary
	// file and reading that file in a FileReader
	private FileReader createFileReader(char[] chars) throws IOException
	{
		File tempFile = File.createTempFile("cfr", "chars");

		FileWriter fos = new FileWriter(tempFile);
		fos.write(chars);
		fos.close();

		return new FileReader(tempFile);
	}

	// checks whether the given (custom) InputStream type (valueType) has a
	// constructor that takes an InputStream type argument. If so, this method
	// tries to create a new instance of that custom InputStream type by invoking
	// the constructor with InputStream type argument. If such constructor
	// does not exist, this method returns a null object.
	private <T extends InputStream> T createCustomInputStream(byte[] bytes, Class<T> valueType)
	{
		try
		{
			return valueType.getConstructor(
					InputStream.class).newInstance(
							new ByteArrayInputStream(bytes));
		}
		catch(Exception e)
		{
			return null;
		}
	}

	// checks whether the given (custom) Reader type (valueType) has a
	// constructor that takes an Reader type argument. If so, this method
	// tries to create a new instance of that custom Reader type by invoking
	// the constructor with Reader type argument. If such constructor
	// does not exist, this method returns a null object.
	private <T extends Reader> T createCustomReader(char[] chars, Class<T> valueType)
	{
		try
		{
			return valueType.getConstructor(
					Reader.class).newInstance(
							new CharArrayReader(chars));
		}
		catch(Exception e)
		{
			return null;
		}
	}
}